package com.xh3140.android.widget.sliding;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.xh3140.android.widget.R;

@SuppressWarnings("unused")
public class SlidingDrawer extends ViewGroup {
    public static final int ORIENTATION_HORIZONTAL = 0;
    public static final int ORIENTATION_VERTICAL = 1;

    private static final int TAP_THRESHOLD = 6;
    private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
    private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
    private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
    private static final float MAXIMUM_ACCELERATION = 2000.0f;
    private static final int VELOCITY_UNITS = 1000;
    private static final int ANIMATION_FRAME_DURATION = 1000 / 60;

    private static final int EXPANDED_FULL_OPEN = -10001;
    private static final int COLLAPSED_FULL_CLOSED = -10002;

    private final boolean mVertical;
    private final boolean mAllowSingleTap;
    private final boolean mAnimateOnClick;
    private final int mBottomOffset;
    private final int mTopOffset;
    private final int mHandleId;
    private final int mContentId;

    private View mHandle;
    private View mContent;

    private Bitmap mBitmap;
    private Canvas mCanvas;

    private int mHandleHeight;
    private int mHandleWidth;

    private int mTouchDelta;

    private boolean mTracking;
    private boolean mLocked;
    private boolean mExpanded;
    private boolean mAnimating;

    private VelocityTracker mVelocityTracker;
    private OnDrawerOpenListener mOnDrawerOpenListener;
    private OnDrawerCloseListener mOnDrawerCloseListener;
    private OnDrawerScrollListener mOnDrawerScrollListener;

    private float mAnimatedAcceleration;
    private float mAnimatedVelocity;
    private float mAnimationPosition;
    private long mAnimationLastTime;
    private long mCurrentAnimationTime;

    private final int mTapThreshold;
    private final int mMaximumTapVelocity;
    private final int mMaximumMinorVelocity;
    private final int mMaximumMajorVelocity;
    private final int mMaximumAcceleration;
    private final int mVelocityUnits;

    private final Rect mRect = new Rect();
    private final Runnable mSlidingRunnable = new Runnable() {
        @Override
        public void run() {
            if (mAnimating) {
                incrementAnimation();
                if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
                    mAnimating = false;
                    closeDrawer();
                } else if (mAnimationPosition < mTopOffset) {
                    mAnimating = false;
                    openDrawer();
                } else {
                    moveHandle((int) mAnimationPosition);
                    mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
                    postDelayed(mSlidingRunnable, ANIMATION_FRAME_DURATION);
                }
            }
        }
    };

    public interface OnDrawerOpenListener {
        void onDrawerOpened();
    }

    public interface OnDrawerCloseListener {
        void onDrawerClosed();
    }

    public interface OnDrawerScrollListener {
        void onScrollStarted();

        void onScrollEnded();
    }

    public SlidingDrawer(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlidingDrawer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public SlidingDrawer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingDrawer, defStyleAttr, defStyleRes);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            saveAttributeDataForStyleable(context, R.styleable.SlidingDrawer, attrs, a, defStyleAttr, 0);
        }
        try {
            mVertical = a.getInt(R.styleable.SlidingDrawer_orientation, ORIENTATION_VERTICAL) == ORIENTATION_VERTICAL;
            mAnimateOnClick = a.getBoolean(R.styleable.SlidingDrawer_animateOnClick, true);
            mAllowSingleTap = a.getBoolean(R.styleable.SlidingDrawer_allowSingleTap, true);
            mTopOffset = a.getDimensionPixelSize(R.styleable.SlidingDrawer_topOffset, 0);
            mBottomOffset = a.getDimensionPixelSize(R.styleable.SlidingDrawer_bottomOffset, 0);
            final int handleId = a.getResourceId(R.styleable.SlidingDrawer_handle, 0);
            if (handleId == 0) {
                throw new IllegalArgumentException("The handle attribute is required and must refer to a valid child.");
            }
            final int contentId = a.getResourceId(R.styleable.SlidingDrawer_content, 0);
            if (contentId == 0) {
                throw new IllegalArgumentException("The content attribute is required and must refer to a valid child.");
            }
            if (handleId == contentId) {
                throw new IllegalArgumentException("The content and handle attributes must refer to different children.");
            }
            mHandleId = handleId;
            mContentId = contentId;
            final float density = getResources().getDisplayMetrics().density;
            mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
            mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
            mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
            mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
            mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
            mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
        } finally {
            a.recycle();
        }
    }

    private class DrawerToggler implements OnClickListener {
        public void onClick(View v) {
            if (mLocked) {
                return;
            }
            if (mAnimateOnClick) {
                animateToggle();
            } else {
                toggle();
            }
        }
    }

    private class ContentViewBitmapCache implements ViewTreeObserver.OnGlobalLayoutListener {

        @Override
        public void onGlobalLayout() {
            final int contentWidth = mContent.getWidth();
            final int contentHeight = mContent.getHeight();
            if (contentWidth > 0 && contentHeight > 0) {
                mBitmap = Bitmap.createBitmap(contentWidth, contentHeight, Bitmap.Config.ARGB_8888);
                mCanvas = new Canvas(mBitmap);
            }
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mHandle = findViewById(mHandleId);
        if (mHandle == null) {
            throw new IllegalArgumentException("The handle attribute is must refer to an existing child.");
        }
        mHandle.setFocusable(true);
        mHandle.setClickable(true);
        mHandle.setOnClickListener(new DrawerToggler());
        mContent = findViewById(mContentId);
        if (mContent == null) {
            throw new IllegalArgumentException("The content attribute is must refer to an existing child.");
        }
        mContent.getViewTreeObserver().addOnGlobalLayoutListener(new ContentViewBitmapCache());
        mContent.setVisibility(View.GONE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
            throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
        }
        measureChild(mHandle, widthMeasureSpec, heightMeasureSpec);
        final int widthMeasureSpecContent;
        final int heightMeasureSpecContent;
        if (mVertical) {
            final int height = heightSpecSize - mHandle.getMeasuredHeight() - mTopOffset;
            widthMeasureSpecContent = MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY);
            heightMeasureSpecContent = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
            mContent.measure(widthMeasureSpecContent, heightMeasureSpecContent);
        } else {
            final int width = widthSpecSize - mHandle.getMeasuredWidth() - mTopOffset;
            widthMeasureSpecContent = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
            heightMeasureSpecContent = MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY);
        }
        mContent.measure(widthMeasureSpecContent, heightMeasureSpecContent);
        setMeasuredDimension(widthSpecSize, heightSpecSize);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        final long drawingTime = getDrawingTime();
        drawChild(canvas, mHandle, drawingTime);
        if (mTracking || mAnimating) {
            mCanvas.translate(-mContent.getScrollX(), -mContent.getScrollY());
            mContent.draw(mCanvas);
            if (mVertical) {
                canvas.drawBitmap(mBitmap, 0f, mHandle.getBottom(), null);
            } else {
                canvas.drawBitmap(mBitmap, mHandle.getRight(), 0f, null);
            }
        } else if (mExpanded) {
            drawChild(canvas, mContent, drawingTime);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (mTracking) {
            return;
        }
        final int width = right - left;
        final int height = bottom - top;
        final int childLeft, childTop;
        int childWidth = mHandle.getMeasuredWidth();
        int childHeight = mHandle.getMeasuredHeight();
        if (mVertical) {
            childLeft = (width - childWidth) / 2;
            childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset;
            mContent.layout(0, mTopOffset + childHeight, mContent.getMeasuredWidth(), mTopOffset + childHeight + mContent.getMeasuredHeight());
        } else {
            childLeft = mExpanded ? mTopOffset : width - childWidth + mBottomOffset;
            childTop = (height - childHeight) / 2;
            mContent.layout(mTopOffset + childWidth, 0, mTopOffset + childWidth + mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
        }
        mHandle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
        mHandleHeight = mHandle.getHeight();
        mHandleWidth = mHandle.getWidth();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mLocked) {
            return false;
        }
        final int action = event.getAction();
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        mHandle.getHitRect(mRect);
        if (!mTracking && !mRect.contains(x, y)) {
            return false;
        }
        if (action == MotionEvent.ACTION_DOWN) {
            mTracking = true;
            mHandle.setPressed(true);
            prepareContent();
            if (mOnDrawerScrollListener != null) {
                mOnDrawerScrollListener.onScrollStarted();
            }
            if (mVertical) {
                final int top = mHandle.getTop();
                mTouchDelta = y - top;
                prepareTracking(top);
            } else {
                final int left = mHandle.getLeft();
                mTouchDelta = x - left;
                prepareTracking(left);
            }
            if (mVelocityTracker != null) {
                mVelocityTracker.addMovement(event);
            }
        }
        return true;
    }

    @Override
    public boolean performClick() {
        return super.performClick();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mLocked) {
            return true;
        }
        if (mTracking) {
            if (mVelocityTracker == null) {
                return false;
            }
            mVelocityTracker.addMovement(event);
            final int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL: {
                    performClick();
                    mVelocityTracker.computeCurrentVelocity(mVelocityUnits);
                    float yVelocity = mVelocityTracker.getYVelocity();
                    float xVelocity = mVelocityTracker.getXVelocity();
                    final boolean negative;
                    if (mVertical) {
                        negative = yVelocity < 0;
                        if (xVelocity < 0) {
                            xVelocity = -xVelocity;
                        }
                        if (xVelocity > mMaximumMinorVelocity) {
                            xVelocity = mMaximumMinorVelocity;
                        }
                    } else {
                        negative = xVelocity < 0;
                        if (yVelocity < 0) {
                            yVelocity = -yVelocity;
                        }
                        if (yVelocity > mMaximumMinorVelocity) {
                            yVelocity = mMaximumMinorVelocity;
                        }
                    }
                    float velocity = (float) Math.hypot(xVelocity, yVelocity);
                    if (negative) {
                        velocity = -velocity;
                    }
                    final int top = mHandle.getTop();
                    final int left = mHandle.getLeft();
                    if (Math.abs(velocity) < mMaximumTapVelocity) {
                        if (mVertical ? (mExpanded && top < mTapThreshold + mTopOffset)
                                || (!mExpanded && top > mBottomOffset + getBottom() - getTop() - mHandleHeight - mTapThreshold)
                                : (mExpanded && left < mTapThreshold + mTopOffset)
                                || (!mExpanded && left > mBottomOffset + getRight() - getLeft() - mHandleWidth - mTapThreshold)) {
                            if (mAllowSingleTap) {
                                playSoundEffect(SoundEffectConstants.CLICK);
                                if (mExpanded) {
                                    animateClose(mVertical ? top : left, true);
                                } else {
                                    animateOpen(mVertical ? top : left, true);
                                }
                            } else {
                                performFling(mVertical ? top : left, velocity, false, true);
                            }
                        } else {
                            performFling(mVertical ? top : left, velocity, false, true);
                        }
                    } else {
                        performFling(mVertical ? top : left, velocity, false, true);
                    }
                }
                break;
            }
        }
        return mTracking || mAnimating || super.onTouchEvent(event);
    }

    private void animateClose(int position, boolean notifyScrollListener) {
        prepareTracking(position);
        performFling(position, mMaximumAcceleration, true, notifyScrollListener);
    }

    private void animateOpen(int position, boolean notifyScrollListener) {
        prepareTracking(position);
        performFling(position, -mMaximumAcceleration, true, notifyScrollListener);
    }

    private void performFling(int position, float velocity, boolean always, boolean notifyScrollListener) {
        mAnimationPosition = position;
        mAnimatedVelocity = velocity;

        if (mExpanded) {
            if (always || (velocity > mMaximumMajorVelocity
                    || (position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth)
                    && velocity > -mMaximumMajorVelocity))) {
                // We are expanded, but they didn't move sufficiently to cause
                // us to retract.  Animate back to the expanded position.
                mAnimatedAcceleration = mMaximumAcceleration;
                if (velocity < 0) {
                    mAnimatedVelocity = 0;
                }
            } else {
                // We are expanded and are now going to animate away.
                mAnimatedAcceleration = -mMaximumAcceleration;
                if (velocity > 0) {
                    mAnimatedVelocity = 0;
                }
            }
        } else {
            if (!always && (velocity > mMaximumMajorVelocity
                    || (position > (mVertical ? getHeight() : getWidth()) / 2
                    && velocity > -mMaximumMajorVelocity))) {
                // We are collapsed, and they moved enough to allow us to expand.
                mAnimatedAcceleration = mMaximumAcceleration;
                if (velocity < 0) {
                    mAnimatedVelocity = 0;
                }
            } else {
                // We are collapsed, but they didn't move sufficiently to cause
                // us to retract.  Animate back to the collapsed position.
                mAnimatedAcceleration = -mMaximumAcceleration;
                if (velocity > 0) {
                    mAnimatedVelocity = 0;
                }
            }
        }

        long now = SystemClock.uptimeMillis();
        mAnimationLastTime = now;
        mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
        mAnimating = true;
        removeCallbacks(mSlidingRunnable);
        postDelayed(mSlidingRunnable, ANIMATION_FRAME_DURATION);
        stopTracking(notifyScrollListener);
    }

    private void prepareTracking(int position) {
        mTracking = true;
        mVelocityTracker = VelocityTracker.obtain();
        boolean opening = !mExpanded;
        if (opening) {
            mAnimatedAcceleration = mMaximumAcceleration;
            mAnimatedVelocity = mMaximumMajorVelocity;
            mAnimationPosition = mBottomOffset + (mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
            moveHandle((int) mAnimationPosition);
            mAnimating = true;
            removeCallbacks(mSlidingRunnable);
            long now = SystemClock.uptimeMillis();
            mAnimationLastTime = now;
            mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
            mAnimating = true;
        } else {
            if (mAnimating) {
                mAnimating = false;
                removeCallbacks(mSlidingRunnable);
            }
            moveHandle(position);
        }
    }

    private void moveHandle(int position) {
        if (mVertical) {
            if (position == EXPANDED_FULL_OPEN) {
                mHandle.offsetTopAndBottom(mTopOffset - mHandle.getTop());
                invalidate();
            } else if (position == COLLAPSED_FULL_CLOSED) {
                mHandle.offsetTopAndBottom(mBottomOffset + getBottom() - getTop() - mHandleHeight - mHandle.getTop());
                invalidate();
            } else {
                final int top = mHandle.getTop();
                int deltaY = position - top;
                if (position < mTopOffset) {
                    deltaY = mTopOffset - top;
                } else if (deltaY > mBottomOffset + getBottom() - getTop() - mHandleHeight - top) {
                    deltaY = mBottomOffset + getBottom() - getTop() - mHandleHeight - top;
                }
                mHandle.offsetTopAndBottom(deltaY);
                mHandle.getHitRect(mRect);
                invalidate();
            }
        } else {
            if (position == EXPANDED_FULL_OPEN) {
                mHandle.offsetLeftAndRight(mTopOffset - mHandle.getLeft());
                invalidate();
            } else if (position == COLLAPSED_FULL_CLOSED) {
                mHandle.offsetLeftAndRight(mBottomOffset + getRight() - getLeft() - mHandleWidth - mHandle.getLeft());
                invalidate();
            } else {
                final int left = mHandle.getLeft();
                int deltaX = position - left;
                if (position < mTopOffset) {
                    deltaX = mTopOffset - left;
                } else if (deltaX > mBottomOffset + getRight() - getLeft() - mHandleWidth - left) {
                    deltaX = mBottomOffset + getRight() - getLeft() - mHandleWidth - left;
                }
                mHandle.offsetLeftAndRight(deltaX);
                mHandle.getHitRect(mRect);
                invalidate();
            }
        }
    }

    private void prepareContent() {
        if (mAnimating) {
            return;
        }
        // Something changed in the content, we need to honor the layout request
        // before creating the cached bitmap
        if (mContent.isLayoutRequested()) {
            if (mVertical) {
                final int childHeight = mHandleHeight;
                final int height = getBottom() - getTop() - childHeight - mTopOffset;
                final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(getRight() - getLeft(), MeasureSpec.EXACTLY);
                final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
                mContent.measure(widthMeasureSpec, heightMeasureSpec);
                mContent.layout(0, mTopOffset + childHeight, mContent.getMeasuredWidth(), mTopOffset + childHeight + mContent.getMeasuredHeight());
            } else {
                final int childWidth = mHandle.getWidth();
                final int width = getRight() - getLeft() - childWidth - mTopOffset;
                final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
                final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(getBottom() - getTop(), MeasureSpec.EXACTLY);
                mContent.measure(widthMeasureSpec, heightMeasureSpec);
                mContent.layout(childWidth + mTopOffset, 0, mTopOffset + childWidth + mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
            }
        }
        // Try only once... we should really loop but it's not a big deal
        // if the draw was cancelled, it will only be temporary anyway
        mContent.getViewTreeObserver().dispatchOnPreDraw();
        mContent.setVisibility(View.GONE);
    }

    private void stopTracking(boolean notifyScrollListener) {
        mHandle.setPressed(false);
        mTracking = false;

        if (notifyScrollListener && mOnDrawerScrollListener != null) {
            mOnDrawerScrollListener.onScrollEnded();
        }

        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private void incrementAnimation() {
        long now = SystemClock.uptimeMillis();
        float t = (now - mAnimationLastTime) / 1000.0f;                   // ms -> s
        final float v = mAnimatedVelocity;                                // px/s
        final float a = mAnimatedAcceleration;                            // px/s/s
        mAnimationPosition += (v * t) + (0.5f * a * t * t);               // px
        mAnimatedVelocity = v + (a * t);                                  // px/s
        mAnimationLastTime = now;                                         // ms
    }

    public void toggle() {
        if (!mExpanded) {
            openDrawer();
        } else {
            closeDrawer();
        }
        invalidate();
        requestLayout();
    }

    public void animateToggle() {
        if (!mExpanded) {
            animateOpen();
        } else {
            animateClose();
        }
    }

    public void open() {
        openDrawer();
        invalidate();
        requestLayout();
        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    }

    public void close() {
        closeDrawer();
        invalidate();
        requestLayout();
    }

    public void animateClose() {
        prepareContent();
        if (mOnDrawerScrollListener != null) {
            mOnDrawerScrollListener.onScrollStarted();
        }
        animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft(), false);
        if (mOnDrawerScrollListener != null) {
            mOnDrawerScrollListener.onScrollEnded();
        }
    }

    public void animateOpen() {
        prepareContent();
        if (mOnDrawerScrollListener != null) {
            mOnDrawerScrollListener.onScrollStarted();
        }
        animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft(), false);
        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
        if (mOnDrawerScrollListener != null) {
            mOnDrawerScrollListener.onScrollEnded();
        }
    }

    @Override
    public CharSequence getAccessibilityClassName() {
        return SlidingDrawer.class.getName();
    }

    private void closeDrawer() {
        moveHandle(COLLAPSED_FULL_CLOSED);
        mContent.setVisibility(View.GONE);
        mContent.destroyDrawingCache();
        if (!mExpanded) {
            return;
        }
        mExpanded = false;
        if (mOnDrawerCloseListener != null) {
            mOnDrawerCloseListener.onDrawerClosed();
        }
    }

    private void openDrawer() {
        moveHandle(EXPANDED_FULL_OPEN);
        mContent.setVisibility(View.VISIBLE);
        if (mExpanded) {
            return;
        }
        mExpanded = true;
        if (mOnDrawerOpenListener != null) {
            mOnDrawerOpenListener.onDrawerOpened();
        }
    }

    public void setOnDrawerOpenListener(@Nullable OnDrawerOpenListener listener) {
        mOnDrawerOpenListener = listener;
    }

    public void setOnDrawerCloseListener(@Nullable OnDrawerCloseListener listener) {
        mOnDrawerCloseListener = listener;
    }

    public void setOnDrawerScrollListener(@Nullable OnDrawerScrollListener listener) {
        mOnDrawerScrollListener = listener;
    }

    @NonNull
    public View getHandle() {
        return mHandle;
    }

    @NonNull
    public View getContent() {
        return mContent;
    }

    public void unlock() {
        mLocked = false;
    }

    public void lock() {
        mLocked = true;
    }

    public boolean isOpened() {
        return mExpanded;
    }

    public boolean isMoving() {
        return mTracking || mAnimating;
    }
}
